﻿using System.Collections.Generic;

namespace Godot.core
{
    using SysMethodInfo = System.Reflection.MethodInfo;
    using SysBindingFlags = System.Reflection.BindingFlags;

    public class ObjectTypeDB
    {
        public class PropertySetGet
        {
            public int index;
            public StringName setter;
            public StringName getter;
            public MethodBind _setptr;
            public MethodBind _getptr;
        };

        public class TypeInfo
        {
            public TypeInfo inherits_ptr;
            public Dictionary<StringName, MethodBind> method_map;
            public Dictionary<StringName, int> constant_map;
            public Dictionary<StringName, MethodInfo> signal_map;
            public Dictionary<StringName, PropertySetGet> property_setget;

            public List<PropertyInfo> property_list;
            public StringName inherits;
		    public StringName name;
		    public bool disabled;
		    
            public delegate Object ObjCtor();
            public ObjCtor creation_func;

            public TypeInfo() 
            {
                method_map = new Dictionary<StringName, MethodBind>();
                constant_map = new Dictionary<StringName, int>();
                signal_map = new Dictionary<StringName, MethodInfo>();
                property_setget = new Dictionary<StringName, PropertySetGet>();
                property_list = new List<PropertyInfo>();
            }
        }

        internal static Object creator<T>() 
            where T : Object, new()
        {
            return (Object) new T();
        }

        protected static Dictionary<StringName, TypeInfo> _types = new Dictionary<StringName,TypeInfo>();

        public static void init()
        {
        }

        public static Object instance(string p_type) 
        {
	
	        TypeInfo ti = null;
	        {
		        //OBJTYPE_LOCK;
		        ti = _types[p_type];
                if (ti == null) return null;
                if (ti.disabled) return null;
                if (ti.creation_func == null) return null;
	        }

	        return ti.creation_func();
        }

        public static string type_inherits_from(string p_type)
        {
            //OBJTYPE_LOCK;
	
	        TypeInfo ti = _types[p_type];
	        if(ti == null)
                return "";
	        return ti.inherits;
        }

        public static bool is_type(string p_type, string p_inherits)
        {
            StringName inherits = p_type;
	
	        while (((string)inherits).Length != 0) {
		
		        if (inherits == p_inherits)
			        return true;
		        inherits = type_inherits_from(inherits);
	        }
	
	        return false;
        }

        public static void bind_integer_constant(StringName p_type, StringName p_name, int p_constant) 
        {
	        //OBJTYPE_LOCK;
	        TypeInfo type = _types[p_type];
	        if (type == null) 
            {
		        return;
	        }
	
	        if (type.constant_map.ContainsKey(p_name)) 
            {
		        return;
	        }
	
	        type.constant_map[p_name]= p_constant;
        }

        public static int get_integer_constant(StringName p_type, StringName p_name, ref bool p_success) 
        {
	        //OBJTYPE_LOCK;
	
	
	        TypeInfo type = _types[p_type];
	
	        while(type != null) 
            {
		        int constant = type.constant_map[p_name];
		        if (constant != 0) 
                {	
				    p_success = true;
			        return constant;
		        }
		
		        type = type.inherits_ptr;
	        }
 
		    p_success=false;	

	        return 0;	
        }

        public static MethodBind get_method(StringName p_type, StringName p_name) 
        {
	        //OBJTYPE_LOCK;
	
	        TypeInfo type = _types[p_type];
	
	        while(type  != null) 
            {
                MethodBind method = null; 
                type.method_map.TryGetValue(p_name, out method);
		        if (method != null)
			        return method;
		        type = type.inherits_ptr;
	        }
	        return null;
        }

        public static MethodBind bind_method(StringName p_name, SysMethodInfo p_func, 
            Variant p_def1 = null, Variant p_def2 = null, Variant p_def3 = null, Variant p_def4 = null, Variant p_def5 = null)
        {
            if (p_func == null) throw new System.NotImplementedException();

            MethodBind mb = MethodBind.create_method_bind(p_func);
            List<Variant> args = new List<Variant>();
            
            if (p_def1 != null) args.Add(p_def1);
            else goto dobind;

            if (p_def2 != null) args.Add(p_def2);
            else goto dobind;

            if (p_def3 != null) args.Add(p_def3);
            else goto dobind;

            if (p_def4 != null) args.Add(p_def4);
            else goto dobind;

            if (p_def5 != null) args.Add(p_def5);

            /*label*/dobind:
            return bind_methodfi(MethodFlags.METHOD_FLAGS_DEFAULT, mb, p_name, (args.Count > 0) ? args.ToArray(): null);
        }

        public static MethodBind bind_method(StringName p_name, System.Delegate p_func)
        {
            MethodBind mb = MethodBind.create_method_bind((System.Delegate)p_func);
            return bind_methodfi(MethodFlags.METHOD_FLAGS_DEFAULT, mb, p_name, null);
        }

        public static MethodBind bind_methodfi(uint p_flags, MethodBind p_bind , string method_name, Variant[] p_defs)
        {
            StringName mdname = method_name;
            StringName rettype;
	        if (((string)mdname).IndexOf(":") != -1) 
            {
		        rettype = ((string)mdname).Split(new char[]{':'})[1];
		        mdname = ((string)mdname).Split(new char[]{':'})[0];
	        }


        	//OBJTYPE_LOCK;
	        if(p_bind ==null)
                return null;

	        p_bind.name = mdname;

	        string instance_type = p_bind.get_instance_type();

	        TypeInfo type = _types[instance_type];
	        if (type == null) 
            {
		        //print_line("couldn't bind method "+mdname+" for instance: "+instance_type);
		        //memdelete(p_bind);
		        return null;
	        }

            type.method_map[mdname] = p_bind;

            List<Variant> defvals = new List<Variant>();

            for (int i = 0; i < p_defs.Length; i++)
            {
                defvals.Add(p_defs[p_defs.Length - i - 1]);
            }

            /*label*/set_defvals: 

            p_bind.default_arguments = defvals;
            p_bind.hint_flags = p_flags;
            return p_bind;
        }

        public static MethodBind bind_native_method<T>(uint p_flags, StringName p_name, SysMethodInfo p_method, MethodInfo p_info = null, List<Variant> p_default_args = null)
        {
            //GLOBAL_LOCK_FUNCTION;

		    MethodBind bind = MethodBind.create_native_method_bind<T>(p_method, p_info);
		    
            if(bind == null) return null;

		    bind.name = p_name;
		    bind.default_arguments = p_default_args;

		    string instance_type = bind.get_instance_type();

		    TypeInfo type = _types[instance_type];
		    if (type == null) 
            {
			    return null;
		    }

		    if (type.method_map.ContainsKey(p_name)) 
            {
                return null;
		    }

		    type.method_map[p_name] = bind;

		    return bind;
        }

	    public static void register_type<T>() where T : Object, new()
        {	
		    //GLOBAL_LOCK_FUNCTION;
            SysMethodInfo mtd_initialize_type = typeof(T).GetMethod("initialize_type", Misc.BindingFlags_Greedy);
            SysMethodInfo mtd_get_type_static = typeof(T).GetMethod("get_type_static", Misc.BindingFlags_Greedy);
            SysMethodInfo mtd_register_custom_data_to_otdb = typeof(T).GetMethod("register_custom_data_to_otdb", Misc.BindingFlags_Greedy);

            if(mtd_initialize_type == null 
            || mtd_get_type_static == null
            || mtd_register_custom_data_to_otdb == null)
                throw new System.NotImplementedException();

		    mtd_initialize_type.Invoke(null, null);
		    TypeInfo t = _types[(string)mtd_get_type_static.Invoke(null, null)];
		    if(t == null) return;
		    t.creation_func = ObjectTypeDB.creator<T>;
		    mtd_register_custom_data_to_otdb.Invoke(null, null);
	    }

      	public static void register_virtual_type<T>() 
        {	
		    //GLOBAL_LOCK_FUNCTION;
		    SysMethodInfo mtd_initialize_type = typeof(T).GetMethod("initialize_type", Misc.BindingFlags_Greedy);

            if(mtd_initialize_type == null)
                throw new System.NotImplementedException();

            mtd_initialize_type.Invoke(null, null);
		    //nothing
	    }

        // DO NOT USE THIS!!!!!! NEEDS TO BE PUBLIC BUT DO NOT USE NO MATTER WHAT!!!
        internal static void _add_type<T>()
        {
            SysMethodInfo mtd_get_type_static = typeof(T).GetMethod("get_type_static");
            SysMethodInfo mth_get_parent_type_static = typeof(T).GetMethod("get_parent_type_static");

            if(mtd_get_type_static == null || mth_get_parent_type_static == null)
                throw new System.NotImplementedException(); // alert !!!

            ObjectTypeDB._add_type2((string)mtd_get_type_static.Invoke(null, null), (string)mth_get_parent_type_static.Invoke(null, null));
        }

        private static void _add_type2(StringName p_type, StringName p_inherits) 
        {
	        //OBJTYPE_LOCK;

	        StringName name = p_type;

	        if(_types.ContainsKey(name))
                return;

	        _types[name] = new TypeInfo();
	        TypeInfo ti = _types[name];
	        ti.name = name;
	        ti.inherits = p_inherits;

	        if (ti.inherits != null) 
            {
		        if(!_types.ContainsKey(ti.inherits)); //it MUST be registered.
		            ti.inherits_ptr = _types[ti.inherits];
	        } 
            else 
            {
		        ti.inherits_ptr = null;
	        }


        }

        public static bool type_exists(string p_type) 
        {	
	        //OBJTYPE_LOCK;
	        return _types.ContainsKey(p_type);	
        }

        public static void add_signal(StringName p_type, MethodInfo p_signal) 
        {
	        TypeInfo type = _types[p_type];

            if (type == null) return;

	        TypeInfo check = type;
	        
            StringName sname = p_signal.name;

	        type.signal_map[sname] = p_signal;
        }

        public static void add_property(StringName p_type, PropertyInfo p_pinfo, StringName p_setter, StringName p_getter, int p_index = -1) 
        {
	        TypeInfo type = _types[p_type];
	        
            if(type == null) return;

	        MethodBind mb_set = null;
	        
            if (p_setter != null) 
            {
		        mb_set = get_method(p_type,p_setter);
                //#ifdef DEBUG_METHODS_ENABLED
                //        if (!mb_set) {
                //            ERR_EXPLAIN("Invalid Setter: "+p_type+"::"+p_setter+" for property: "+p_pinfo.name);
                //            ERR_FAIL_COND(!mb_set);
                //        } else {
                //            int exp_args=1+(p_index>=0?1:0);
                //            if (mb_set->get_argument_count()!=exp_args) {
                //                ERR_EXPLAIN("Invalid Function for Setter: "+p_type+"::"+p_setter+" for property: "+p_pinfo.name);
                //                ERR_FAIL();

                //            }
                //        }
                //#endif
	        }

            MethodBind mb_get = null;
	        if (p_getter != null) 
            {
		        mb_get = get_method(p_type, p_getter);
                //#ifdef DEBUG_METHODS_ENABLED

                //        if (!mb_get) {
                //            ERR_EXPLAIN("Invalid Getter: "+p_type+"::"+p_getter+" for property: "+p_pinfo.name);
                //            ERR_FAIL_COND(!mb_get);
                //        } else {

                //            int exp_args=0+(p_index>=0?1:0);
                //            if (mb_get->get_argument_count()!=exp_args) {
                //                ERR_EXPLAIN("Invalid Function for Getter: "+p_type+"::"+p_getter+" for property: "+p_pinfo.name);
                //                ERR_FAIL();

                //            }

                //        }
                //#endif
	        }



            //#ifdef DEBUG_METHODS_ENABLED

            //    if (type->property_setget.has(p_pinfo.name)) {
            //        ERR_EXPLAIN("Object already has property: "+p_type);
            //        ERR_FAIL();
            //    }
            //#endif
	        
            type.property_list.Add(p_pinfo);

	        PropertySetGet psg = new PropertySetGet();
	        psg.setter = p_setter;
	        psg.getter = p_getter;
	        psg._setptr = mb_set;
	        psg._getptr = mb_get;
	        psg.index = p_index;

	        type.property_setget[p_pinfo.name] = psg;
        }

        public static bool set_property(Object p_object, StringName p_property, Variant p_value) 
        {
            TypeInfo type = null;
            _types.TryGetValue(p_object.get_type_name(), out type);	        
	        TypeInfo check = type;
	        while(check != null) 
            {
                PropertySetGet psg = null;
                check.property_setget.TryGetValue(p_property, out psg);
		        if (psg != null) 
                {
			        if (psg.setter == null)
				        return true; //return true but do nothing

			        if (psg.index >= 0) 
                    {
				        Variant index = psg.index;
				        Variant[] arg= new Variant[2]{ index, p_value};
				        Variant.CallError ce = new Variant.CallError();
				        p_object.call(psg.setter, arg, ref ce);
			        } 
                    else 
                    {
				        Variant[] arg = new Variant[1]{ p_value };
				        Variant.CallError ce = new Variant.CallError();
				        if (psg._setptr != null) 
                        {
					        psg._setptr.call(p_object, arg, ref ce);
				        } 
                        else 
                        {
					        p_object.call(psg.setter, arg,ref ce);
				        }
			        }
			        return true;
		        }

		        check = check.inherits_ptr;
	        }

	        return false;
        }

        public static bool get_property(Object p_object, StringName p_property, ref Variant r_value) 
        {
	        TypeInfo type = _types[p_object.get_type_name()];
	        TypeInfo check = type;
	        while(check != null) 
            {
                PropertySetGet psg = null;
                check.property_setget.TryGetValue(p_property, out psg);
                if (psg != null)
                {
                    if (psg.getter == null)
                        return true; //return true but do nothing

                    if (psg.index >= 0)
                    {
                        Variant index = psg.index;
                        Variant[] arg = new Variant[1] { index };
                        Variant.CallError ce = new Variant.CallError();
                        r_value = p_object.call(psg.getter, arg, ref ce);

                    }
                    else
                    {
                        Variant.CallError ce = new Variant.CallError();
                        if (psg._getptr != null)
                        {
                            r_value = psg._getptr.call(p_object, null, ref ce);
                        }
                        else
                        {
                            r_value = p_object.call(psg.getter, null, ref ce);
                        }
                    }
                    return true;
                }

		        if(check.constant_map.ContainsKey(p_property))
                {
			        r_value = check.constant_map[p_property];
			        return true;
		        }
		        //if (check->constant_map.fin)

		        check = check.inherits_ptr;
	        }

	        return false;
        }

        public static void get_property_list(StringName p_type,ref List<PropertyInfo> p_list, bool p_no_inheritance = false)
        {
            TypeInfo type = _types[p_type];
	        TypeInfo check = type;
	        while(check != null) 
            {
		        foreach(PropertyInfo E in type.property_list) 
                {
			        p_list.Add(E);
		        }

		        if (p_no_inheritance)
			        return ;

		        check = check.inherits_ptr;
	        }
        }

        public static bool has_signal(StringName p_type, StringName p_signal) 
        {
	        TypeInfo type = _types[p_type];
	        TypeInfo check = type;
	        while(check != null) 
            {
		        if (check.signal_map.ContainsKey(p_signal))
			        return true;
		        check = check.inherits_ptr;
	        }
	        return false;
        }

        public static void add_resource_base_extension(StringName p_extension, StringName p_type) 
        {
	        if (__resource_base_extensions.ContainsKey(p_extension))
		        return;

	        __resource_base_extensions[p_extension] = p_type;
        }

        private static Dictionary<StringName, StringName> __resource_base_extensions = new Dictionary<StringName,StringName>();
    }
}
